/**
*  Copyright (c) 2011, Alex Theodoridis
*  All rights reserved.

*  Redistribution and use in source and binary forms, with 
*  or without modification, are permitted provided that the 
*  following conditions are met:
*  Redistributions of source code must retain the above 
*  copyright notice, this list of conditions and the following disclaimer.
*  Redistributions in binary form must reproduce the above 
*  copyright notice, this list of conditions and the following
*  disclaimer in the documentation and/or other materials 
*  provided with the distribution.

*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS
*  AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
*  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
*  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
*  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
*  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
*  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE,
*  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
*/

#ifndef XMLNEURONINSTREAM_H_
#define XMLNEURONINSTREAM_H_
#include <NeuralNetwork/Serialization/INeuronStream.h>
#include <Utilities/SmartPtr/SharedPtr.h>
#include <Utilities/StrUtil/StrUtil.h>
#include <tinyxml/tinyxml.h>
#include <NeuralNetwork/Serialization/NeuronMemento.h>

namespace nn {

    template<class Var>
    class Neuron;

    template<class Var>
    class IXmlVarStream;

    template<class Var>
    class XmlNeuronStream : public INeuronStream<Var> {
    public:
        static const std::string CONST_NEURON_NODE_NAME;
        static const std::string CONST_NEURON_WEIGHT_NODE_NAME;
        static const std::string CONST_NEURON_INPUT_WEIGHT_NODE_NAME;

    private:
        utils::SharedPtr<IXmlVarStream<Var> > _varStream;
        NeuronMemento<Var> _memento;

    public:
        XmlNeuronStream(TiXmlElement * neuronNode, const utils::SharedPtr<IXmlVarStream<Var> >& varInStream) {
            try{
                if (neuronNode == NULL) {
                    throw bad_stream("Wrong argument neuronNode==NULL", __FILE__, __LINE__);
                }

                if ( varInStream == NULL ){
                    throw bad_stream("Wrong argument varInStream==NULL", __FILE__, __LINE__);
                }

                if( CONST_NEURON_NODE_NAME != neuronNode->Value() ){
                    throw bad_stream("Wrong argument neuronNode invalid node name", __FILE__, __LINE__);
                }

                std::map< unsigned int, NeuronInput<Var> > inputs;
                TiXmlElement* weightNode = neuronNode->FirstChildElement (CONST_NEURON_INPUT_WEIGHT_NODE_NAME.c_str() );
                while ( weightNode != NULL ){
                    std::string value = weightNode->Value();
                    Var weight = varInStream->deserialize( weightNode );
                    NeuronInput<Var> input(0, weight);
                    if ( inputs.insert( std::pair<unsigned int, NeuronInput<Var> > ( inputs.size() , input ) ).second == false ) {
                        throw bad_stream("Invalid input weight id", __FILE__, __LINE__);
                    }

                    weightNode=weightNode->NextSiblingElement( CONST_NEURON_INPUT_WEIGHT_NODE_NAME.c_str() );
                }

                TiXmlElement* neuronWeightNode=neuronNode->FirstChildElement( CONST_NEURON_WEIGHT_NODE_NAME.c_str() );
                if( neuronWeightNode==NULL ){
                    throw bad_stream("NeuronWeight not found", __FILE__, __LINE__);
                }

                _memento.setNeuronWeight( varInStream->deserialize( neuronWeightNode ) );
                _memento.setInputs(inputs);
            }catch( const utils::bad_cast& e){
                throw bad_stream( e.what(), __FILE__, __LINE__ );
            }
        }

        XmlNeuronStream( const NeuronMemento<Var>& neuronMemento, const utils::SharedPtr<IXmlVarStream<Var> >& varInStream){
            if ( neuronMemento.getInputs().size() == 0 ){
                throw bad_stream("Invalid argument neuronMemento", __FILE__, __LINE__);
            }

            if( varInStream == NULL ){
                throw bad_stream("Invalid argument varInStream", __FILE__, __LINE__);
            }

            _memento = neuronMemento;
            _varStream = varInStream;
        }

        XmlNeuronStream( const Neuron<Var>& neuron, const utils::SharedPtr<IXmlVarStream<Var> >& varInStream){
            if( _varStream == NULL ){
                throw bad_stream("Invalid argument varInStream", __FILE__, __LINE__);
            }

            _varStream=varInStream;
            _memento=neuron.getMemento();
        }

        const NeuronMemento<Var>& getMemento(){
            return _memento;
        }

        Neuron<Var>& operator >>( Neuron<Var>& neuron) const {
            neuron.setMemento(_memento);
            return neuron;
        }

        const TiXmlDocument& operator >> ( TiXmlDocument& document ){
            document.Clear();
            TiXmlElement* nodeElement = new TiXmlElement( CONST_NEURON_NODE_NAME.c_str() );
            std::map< unsigned int, NeuronInput<Var> > inputs=_memento.getInputs();
            typename std::map< unsigned int, NeuronInput<Var> >::const_iterator curInput;
            for ( curInput = inputs.begin(); curInput != inputs.end(); curInput++ ){
                TiXmlText* text = NULL;
                _varStream->serialize (curInput->second.getInputWeight() , &text);
                TiXmlElement* weightNode = new TiXmlElement( CONST_NEURON_INPUT_WEIGHT_NODE_NAME.c_str() );
                weightNode->LinkEndChild(text);
                nodeElement->LinkEndChild(weightNode);
            }

            TiXmlText* text = NULL;
            _varStream->serialize (_memento.getNeuronWeight() , &text);
            TiXmlElement* weightNode = new TiXmlElement( CONST_NEURON_WEIGHT_NODE_NAME.c_str() );
            weightNode->LinkEndChild(text);
            nodeElement->LinkEndChild(weightNode);
            document.LinkEndChild(nodeElement);
            return document;
        }

        virtual ~XmlNeuronStream() {
        }
    };

    template<class Var>
    const std::string XmlNeuronStream<Var>::CONST_NEURON_NODE_NAME = "Neuron";

    template<class Var>
    const std::string XmlNeuronStream<Var>::CONST_NEURON_WEIGHT_NODE_NAME = "NeuronWeight";

    template<class Var>
    const std::string XmlNeuronStream<Var>::CONST_NEURON_INPUT_WEIGHT_NODE_NAME = "NeuronInput";

}

#endif /* XMLNEURONINSTREAM_H_ */
